//
// Created by hsj on 2023-12-29.
//文件描述符都有一个引用计数特性，由于子进程复制了一份父进程的所有东东，那么描述符的引用计数就+1了，由原来只有父进程的1个引用，变成了2.
//这就表示，close()的时候呢，仅仅只是吧引用计数减1，知道减到0了，才真正的释放该描述符。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>

#ifdef WIN32
//windows minGW64
//inet_pton函数：_WIN32_WINNT
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#define _WIN32_WINNT 0x0600
#include <winsock2.h>
#include <ws2tcpip.h>
//#pragma comment(lib,"ws2_32.lib")
#else
//linux  gcc
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/wait.h>
#include <signal.h>
#endif
#define BUF_SIZE 40
void error_handling( char *message);
void read_routine(int sock, char *buf) ;
void write_routine(int sock, char *buf);
//防止僵尸进程，
void read_childproc(int sig);

int main(int argc, char* argv[]){
    int sock;
    pid_t pid;
    struct sockaddr_in serv_addr;
    char buff_read[BUF_SIZE];
    char buff_writer[BUF_SIZE];
    char ip[]="127.0.0.1";
    char port[]="9988";

    int str_len;

    /**
     * struct sigaction
       {
            void (*sa_handler)(int);
            sigset_t sa_mask;
            int sa_flags;
       }
        此结构体的sa_handler成 员保存信号处理函数的指针值(地址值)。
        sa_mask和sa_flags的所有位均初始化为0即可。这2个成员用于指定信号相关的选项和特性，而我们的目的主要是防止产生僵尸进程，故省略。

     */
    struct sigaction act;
    act.sa_handler=read_childproc;
    sigemptyset(&act.sa_mask);
    act.sa_flags=0;
    //注册子进程结束事件
    sigaction(SIGCHLD,&act, 0);

    /**
     * 在windows中AF_INET与PF_INET完全一样. 而在Unix/Linux系统中，在不同的版本中这两者有微小差别.对于BSD,是AF,对于POSIX是PF.理论上建立socket时是指定协议，应该用PF_xxxx，设置地址时应该用AF_xxxx。
     * int socket(int domain, int type, int protocol);
     * domain:
           AF_INET 这是大多数用来产生socket的协议，使用TCP或UDP来传输，用IPv4的地址
           AF_INET6 与上面类似，不过是来用IPv6的地址
           AF_UNIX 本地协议，使用在Unix和Linux系统上，一般都是当客户端和服务器在同一台及其上的时候使用
       type:
           SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型，这个socket是使用TCP来进行传输。
           SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的，使用UDP来进行它的连接。
           SOCK_SEQPACKET该协议是双线路的、可靠的连接，发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
           SOCK_RAW socket类型提供单一的网络访问，这个socket类型使用ICMP公共协议。（ping、traceroute使用该协议）
           SOCK_RDM 这个类型是很少使用的，在大部分的操作系统上没有实现，它是提供给数据链路层使用，不保证数据包的顺序
       protocol:
           传0 表示使用默认协议。
       返回值：
           成功：返回指向新创建的socket的文件描述符
           失败：返回-1，设置errno
    */
    sock=socket(PF_INET, SOCK_STREAM, 0);
    if(sock == -1)
        error_handling("socket() error" );

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=inet_addr(/*argv[1]*/ip);//等价inet_pton(AF_INET, ip, &serv_addr.sin_addr.s_addr);
    serv_addr.sin_port=htons(atoi(/*argv[2]*/port));

    if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
        error_handling(" connect() error!");

   /* char pc[] = "hello mproce_serve,i am client...";
    write(sock, pc, sizeof(pc));


    memset(message, 0, sizeof(message));
    str_len=read(sock, message, sizeof(message)-1);//少一，保证'\0'结尾
    if(str_len==-1)
        error_handling("read() error!");
    *//**
     * #include <unistd.h>
        ssize_t read(int fd, void *buf, size_t count);

       在非阻塞模式下，数据尚未准备好，则返回错误代码EAGAIN或EWOULDBLOCK。
       文件指针已到达文件末尾，此时返回0。
       出现错误，返回-1。
    *//*
//  while((str_len=read(sock, message, sizeof(message)-1))!=0)//少一，保证'\0'结尾
    printf("Message from mproce_serve : %s, len : %d \n", message,str_len);

    close(sock);*/

    pid=fork();
    if(pid==0) /*子进程运行区域【write】*/
    {
        write_routine(sock, buff_writer);
        return 18;
    }
    else /*父进程运行区域【read】*/
    {
        printf("master : Child proc id: %d \n", pid);
        read_routine(sock, buff_read) ;
    }
    printf("socket master: %d\n", sock);
    close(sock);
    return EXIT_SUCCESS;
}

void read_routine(int sock, char *buf){
    while(1){
        int str_len=read(sock, buf, BUF_SIZE-1);
        if(str_len<=0)
        {
            printf("master read end:%d\n",str_len);
            return;
        }

        buf[str_len]=0;
        printf("Message from server: %s", buf);
    }
}

void write_routine(int sock, char *buf){
    while(1){
        fgets(buf, BUF_SIZE, stdin);//回车、换行-->结束
        if(!strcmp(buf,"q\n") || !strcmp(buf, "Q\n")){//结束，关闭写单向并且结束
            shutdown(sock, SHUT_WR);//建议-->如果想让fd在计数符号>1的情况下发送FIN给对端，那么可以使用shutdown()函数
//            close(sock);//不建议
            printf("socket slave: %d\n", sock);
            return;
        }
        write(sock, buf, strlen(buf));
    }
}

void read_childproc(int sig){
    pid_t pid;
    int status;
    //非阻塞式退出进程
    //waitpid第一个参数：等待终止的目标子进程的ID，若传递-1，则与wait函数相同，可以等待任意子进程终止。
    pid=waitpid(-1, &status, WNOHANG);
    if(WIFEXITED(status)){//检测退出了吗
        printf("signal: Removed Child proc  id: %d \n", pid);
        printf("signal: Child send: %d \n", WEXITSTATUS(status));//获取退出的子进程的退出码/返回值
    }
}

void error_handling( char *message){
    fputs (message, stderr);
    fputc('\n', stderr);
    exit(1);
}